//
// Created by dg on 2024/1/2.
//

#include "YUVRender.h"
#include "Log.h"
extern "C"{
#include "Commontool.h"
};

using namespace std;

static char* NO_FILTER_VERTEX_SHADER =
        "attribute vec4 vPosition;\n"
        "attribute vec4 vTexCords;\n"
        "varying vec2 yuvTexCoords;\n"
        "uniform highp mat4 texMatrix;\n"
        "uniform highp mat4 trans; \n"
        "void main() {\n"
        "  yuvTexCoords = (texMatrix*vTexCords).xy;\n"
        "  gl_Position = trans * vPosition;\n"
        "}\n";

static char* YUV_FRAME_FRAGMENT_SHADER =
        "varying highp vec2 yuvTexCoords;\n"
        "uniform sampler2D s_texture_y;\n"
        "uniform sampler2D s_texture_u;\n"
        "uniform sampler2D s_texture_v;\n"
        "void main(void)\n"
        "{\n"
        "highp float y = texture2D(s_texture_y, yuvTexCoords).r;\n"
        "highp float u = texture2D(s_texture_u, yuvTexCoords).r - 0.5;\n"
        "highp float v = texture2D(s_texture_v, yuvTexCoords).r - 0.5;\n"
        "\n"
        "highp float r = y + 1.402 * v;\n"
        "highp float g = y - 0.344 * u - 0.714 * v;\n"
        "highp float b = y + 1.772 * u;\n"
        "gl_FragColor = vec4(r,g,b,1.0);\n"
        "}\n";


YUVRender::YUVRender(int screenW, int screenH):_screenW(screenW), _screenH(screenH) {
    program = nullptr;
    yuvTextureFrame = nullptr;
}

int YUVRender::prepareRender(int frameWidth, int frameHeight) {
        program = new BLProgram(NO_FILTER_VERTEX_SHADER, YUV_FRAME_FRAGMENT_SHADER);

        program->addAttribute("vPosition");
        program->addAttribute("vTexCords");

        if (!program->link()) {
                LOGE("program link error");
                return -1;
        }

        _positionAttr = program->attributeIndex("vPosition");
        _textureAttr = program->attributeIndex("vTexCords");

        _unformSamplers[0] = program->uniformIndex("s_texture_y");
        _unformSamplers[1] = program->uniformIndex("s_texture_u");
        _unformSamplers[2] = program->uniformIndex("s_texture_v");

        _mUniformTransforms = program->uniformIndex("trans");
        _mUniformTexMatrix = program->uniformIndex("texMatrix");

        program->use();

        glEnableVertexAttribArray(_positionAttr);
        glEnableVertexAttribArray(_textureAttr);

        glGenFramebuffers(1, &_frameBuffer);
        glGenTextures(1, &_outputTextureID);
        glBindTexture(GL_TEXTURE_2D, _outputTextureID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)frameWidth, (int)frameHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _outputTextureID, 0);
        glBindTexture(GL_TEXTURE_2D, 0);

        yuvTextureFrame = new BLYUVTextureFrame();
        yuvTextureFrame->createTexture();
        return 0;
}

int YUVRender::render(uint8_t *frame, int frameWidth, int frameHeight) {
    glBindTexture(GL_TEXTURE_2D, _outputTextureID);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _outputTextureID, 0);

    program->use();

    glViewport(0,0,_screenW, _screenH);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    int readSize = frameWidth * frameHeight + (frameWidth / 2 * frameHeight / 2 ) * 2;
    yuvTextureFrame->setVideoFrame(frame, readSize);
    // 这里还有BUG
    yuvTextureFrame->updateTexImage(frameWidth, frameHeight);

    static const GLfloat imageVertices[] = {
            -1.0f, -1.0f,
            1.0f, -1.0f,
            -1.0f,  1.0f,
            1.0f,  1.0f,
    };

    GLfloat noRotationTextureCoordinates[] = {
            0.0f, 1.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            1.0f, 0.0f,
    };

    glVertexAttribPointer(_positionAttr, 2, GL_FLOAT, 0, 0, imageVertices);
    glEnableVertexAttribArray(_positionAttr);
    glVertexAttribPointer(_textureAttr, 2, GL_FLOAT, 0, 0, noRotationTextureCoordinates);
    glEnableVertexAttribArray(_textureAttr);

    yuvTextureFrame->bindTexture(_unformSamplers);

    float texTransMatrix[4 * 4];
    matrixSetIdentityM(texTransMatrix);
    glUniformMatrix4fv(_mUniformTexMatrix, 1, GL_FALSE, (GLfloat *) texTransMatrix);

    float rotateMatrix[4 * 4];
    matrixSetIdentityM(rotateMatrix);
    glUniformMatrix4fv(_mUniformTransforms, 1, GL_FALSE, (GLfloat *) rotateMatrix);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableVertexAttribArray(_positionAttr);
    glDisableVertexAttribArray(_textureAttr);
    glBindTexture(GL_TEXTURE_2D, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    return 0;
}

void YUVRender::resetRenderSize(int width, int height) {
    _screenH = height;
    _screenW = width;
}

YUVRender::~YUVRender() {
        if (program) {
                delete program;
                program = nullptr;
        }

        if (yuvTextureFrame) {
            yuvTextureFrame->dealloc();
            delete yuvTextureFrame;
            yuvTextureFrame = nullptr;
        }
}